package com.condroid;

import ioio.lib.api.Uart;

import java.io.IOException;
import java.util.Date;
import java.util.LinkedList;

import android.os.Handler;
import android.os.Message;
import android.telephony.SmsManager;
import android.widget.Toast;

/**
 * This class is used to issue command in SMS.
 * @author JUN GAO
 * Condroid Summer CSD 2011 - KTH
 */
public class SMSDealer extends Thread{
	private static LinkedList<SmsSession> sessionList = new LinkedList<SmsSession>();
	private boolean abort = false;
	private IOIOThread ioioThread;
	private CmdCmit cmdCmit;
	private Date time;
	private long timer;
	
	public SMSDealer(){
		ioioThread = IOIOThread.getInstance();
		cmdCmit = new CmdCmit();
		time = new Date();
		timer = time.getTime();
//		if(!ioioThread.isAlive())
//			ioioThread.start();
	}
	
	@Override
	public void run() {
		while (!abort) {
			if (!sessionList.isEmpty()) {
				for (SmsSession s : sessionList) {
					if (s.state == SmsSession.State.CLOSING
							|| s.state == SmsSession.State.CONNECTED) {
						if (!s.cmdList.isEmpty()) {
							if (cmdCmit.setCmd(s.cmdList.getFirst())) {
								cmdCmit.setOutStream(s.outStream);
								try {
									cmdCmit.exeCmd();
								} catch (IOException e) {
									// TODO Auto-generated catch block
									e.printStackTrace();
								} catch (InterruptedException e) {
									// TODO Auto-generated catch block
									e.printStackTrace();
								}
							} else
								msgSend(s.phoneNo, "Problem command: "
										+ s.cmdList.getFirst());
							s.cmdList.remove(s.cmdList.getFirst());
							s.setLastTime();
						}
					}

					if (time.getTime() - s.lastTime > SESSIONDURATIONTIME) {
						s.state = SmsSession.State.CLOSING;
					}

					if (s.state == SmsSession.State.CLOSING)
						disconnect(s);
				}
				timer = time.getTime();
			} else {
				if (time.getTime() - timer > DEALERDURATIONTIME)
					abort = true;
			}
		}
		
		if (!sessionList.isEmpty()) {
			for (SmsSession s : sessionList) {
				s.inUartThread.finish();
				try {
					s.outStream.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				ioioThread.closeUart(s.uart, s.uartInfo);
				sessionList.remove(s);
			}
		}
	}
	
	/**
	 * use this method to add command for each session and the phone number is the
	 * ID for each session
	 * @param phoneNo the phone number of the session
	 * @param cmd the command provided by the user
	 */
	public synchronized void deal(String phoneNo, String cmd){
		SmsSession session = null;
		for(SmsSession s : sessionList){
			if(s.phoneNo.equals(phoneNo)){
				session = s;
				break;
			}
		}
		
		if (session == null) {
			if (!cmd.startsWith("$pas")) {
				msgSend(phoneNo, "Please provide password first!");
				return;
			} else {
				String pas = cmd.substring(4).trim();
				if (!StdBySettings.checkAccessCode(pas)) {
					msgSend(phoneNo, "Password not correct!");
					return;
				}
			}
			session = new SmsSession();
			session.phoneNo = phoneNo;
			session.state = SmsSession.State.UNCONNECT;
			session.setLastTime();
			sessionList.add(session);
		}else{
			switch(session.state){
			case UNCONNECT:
				establishConnection(session, cmd);
				break;
			case CONNECTED:
				process(session, cmd);
				break;
			default:
				break;
			}
		} 
	}
	
	private void establishConnection(SmsSession session, String cmd){
		if(!cmd.startsWith("$cnt")){
			msgSend(session.phoneNo, "Establish connection first!");
			return;
		}else{
			UartInfo ui = parseConnectInfo(cmd.substring(4));
			if(ui == null){
				msgSend(session.phoneNo, "Didn't get UART info!");
				return;
			}
			if(!isInfoOK(ui)){
				msgSend(session.phoneNo, "Pin in use!");
				return;
			}
			session.uartInfo = ui;
			if(!ioioThread.openUart(ui)){
				msgSend(session.phoneNo, "Can not open UART channel!");
				return;
			}else{
				session.uart = ioioThread.getUart();
				if(session.uart == null){
					msgSend(session.phoneNo, "Connection can not set up. Try later!");
					return;
				}
				session.outStream = session.uart.getOutputStream();
				session.state = SmsSession.State.CONNECTED;
				session.inUartThread = new InUartThread(session.uart.getInputStream(), session.phoneNo, ConstantData.ControlType.SMS);
				session.inUartThread.start();
				msgSend(session.phoneNo, "connection established!");
			}
		}
	}
	
	private void process(SmsSession session, String cmd){
		if(cmd.startsWith("$cnt")){
			String[] op = cmd.substring(4).split("-");
			boolean disc = false;
			for(String s : op){
				if(s.equals("d")){
					disc = true;
					break;
				}
			}
			if(disc)
				session.state = SmsSession.State.CLOSING;
			else{
				msgSend(session.phoneNo, "Connection already established, disconnect it before connect again!");
				return;
			}
		}else if(session.state == SmsSession.State.CLOSING){
			msgSend(session.phoneNo, "Connection is closing. Try later!");
			return;
		}else
			session.cmdList.add(cmd);
	}
	
	private void disconnect(SmsSession s){
		if(!s.cmdList.isEmpty())//still have command to execute
			return;
		
		if (s.state == SmsSession.State.CONNECTED) {
			s.inUartThread.finish();
			try {
				s.inUartThread.join();
				s.outStream.close();
			} catch (InterruptedException e) {
				// TODO logging
			} catch (IOException e) {
				// TODO logging
			}
			ioioThread.closeUart(s.uart, s.uartInfo);
		}
		
		sessionList.remove(s);
	}
	
	private boolean isInfoOK(UartInfo info){
		for(SmsSession s : sessionList){
			if(s.uartInfo != null && s.uartInfo.hasSamePin(info))
				return false;
		}
		return true;
	}
	
	private UartInfo parseConnectInfo(String info) throws NumberFormatException {
		Settings.getConfiguration(ConstantData.appContext);
		UartInfo uartInfo = new UartInfo(Settings.getRcvPort(),
				Settings.getTransPort(), Settings.getbaud(),
				Settings.getparity(), Settings.getstopBits());
		String[] op = info.split("-");
		for (String s : op) {
			if (s.startsWith("r")) {
				int rPin = Integer.parseInt(s.substring(1).trim());
				if (rPin > 13 || rPin < 10)
					return null;
				uartInfo.setRxPin(rPin);
			} else if (s.startsWith("t")) {
				int tPin = Integer.parseInt(s.substring(1).trim());
				if (tPin > 6 || tPin < 3)
					return null;
				uartInfo.setTxPin(tPin);
			} else if (s.startsWith("b")) {
				int baud = Integer.parseInt(s.substring(1).trim());
				if (!UartInfo.isBaudOk(baud))
					return null;
				uartInfo.setBaud(baud);
			} else if (s.startsWith("s")) {
				switch (Integer.parseInt(s.substring(1).trim())) {
				case 1:
					uartInfo.setStopBits(Uart.StopBits.ONE);
					break;
				case 2:
					uartInfo.setStopBits(Uart.StopBits.TWO);
					break;
				default:
					return null;

				}
			} else if (s.startsWith("p")) {
				switch (Integer.parseInt(s.substring(1).trim())) {
				case 0:
					uartInfo.setParity(Uart.Parity.NONE);
					break;
				case 1:
					uartInfo.setParity(Uart.Parity.ODD);
					break;
				case 2:
					uartInfo.setParity(Uart.Parity.EVEN);
					break;
				default:
					return null;
				}
			}
		}

		if (uartInfo.isInfoOk())
			return uartInfo;
		else
			return null;
	}
	
	private void msgSend(String phoneNo,String msg){
		SmsManager sms = SmsManager.getDefault();
		sms.sendTextMessage(phoneNo, ConstantData.phoneNo, msg, null, null);
	}
	
//	private Handler smsHandler = new Handler(){
//		@Override
//		public void handleMessage(Message m){
//			ConMessage cm = (ConMessage) m.obj;
//			SmsSession s = null;
//			for(SmsSession se : sessionList){
//				if(se.inUartThread == cm.getInUartThread()){
//					s = se;
//					break;
//				}
//			}
//			if(s != null){
//				msgSend(s.phoneNo, cm.getMsg());
//			}
//		}
//	};
	
	/**
	 * use this method to finish this thread.
	 */
	public synchronized void finish(){
		abort = true;
	}
	
	private static final int SESSIONDURATIONTIME = 60000;//session will auto close if no new command for 1 minutes
	private static final int DEALERDURATIONTIME = 300000;//dealer will auto close if no new session setup for 5 minutes
}
